﻿using System;
using System.IO;
using Gmantis.Zip.ZLib;

namespace Gmantis.Zip
{
    public class CryptStream : Stream
    {
        // Fields
        private Stream _baseStream;
        private IChecksum _crc;
        private long _crcValue;
        private uint[] _keys = new uint[3];
        private string _password;
        private ZipEntry _ze;
        private ZipFile _zip;
        internal const int ENCR_HEADER_LEN = 12;

        // Methods
        internal CryptStream(ZipEntry ze, Stream baseStream)
        {
            this._baseStream = baseStream;
            this._ze = ze;
            this._zip = ze._owner;
            this._password = this._zip._password;
            this._crc = new CRC32();
        }

        public override void Close()
        {
            this._baseStream.Close();
        }

        private bool CryptCheck()
        {
            byte num = 0;
            byte[] buffer = new byte[0x40000];
            this._baseStream.Read(buffer, 0, 12);
            for (int i = 0; i < 12; i++)
            {
                num = this.CryptDecode(buffer[i]);
            }
            if (!this._ze.HasDataDescriptor)
            {
                return (((byte)(this._ze._crc32 >> 0x18)) == num);
            }
            return (((byte)(this._ze._modTime >> 8)) == num);
        }

        private static uint CryptCRC32(uint l, char c)
        {
            return (CRC32.crc_table[(l ^ c) & 0xff] ^ (l >> 8));
        }

        internal void CryptCryptHeader(long crc, byte[] buf)
        {
            byte num;
            this.CryptInitKeys();
            Random random = new Random(DateTime.Now.Millisecond);
            for (int i = 0; i < 10; i++)
            {
                int num3 = random.Next();
                num = (byte)(num3 >> 6);
                if (num == 0)
                {
                    num = (byte)num3;
                }
                buf[i] = this.CryptEncode(num);
            }
            num = (byte)((crc >> 0x10) & 0xffL);
            buf[10] = this.CryptEncode(num);
            num = (byte)((crc >> 0x18) & 0xffL);
            buf[11] = this.CryptEncode(num);
        }

        private byte CryptDecode(byte b)
        {
            b = (byte)(b ^ this.CryptDecryptByte());
            this.CryptUpdateKeys((char)b);
            return b;
        }

        internal void CryptDecodeBuffer(byte[] buf, int count)
        {
            for (int i = 0; i < count; i++)
            {
                buf[i] = this.CryptDecode(buf[i]);
            }
        }

        private byte CryptDecryptByte()
        {
            uint num = (this._keys[2] & 0xffff) | 2;
            return (byte)(((num * (num ^ 1)) >> 8) & 0xff);
        }

        internal byte CryptEncode(byte b)
        {
            byte num = this.CryptDecryptByte();
            this.CryptUpdateKeys((char)b);
            b = (byte)(b ^ num);
            return b;
        }

        internal void CryptEncodeBuffer(byte[] buf, int offset, int count)
        {
            for (int i = 0; i < count; i++)
            {
                buf[offset + i] = this.CryptEncode(buf[offset + i]);
            }
        }

        private void CryptInitKeys()
        {
            this._keys[0] = 0x12345678;
            this._keys[1] = 0x23456789;
            this._keys[2] = 0x34567890;
            for (int i = 0; i < this._password.Length; i++)
            {
                this.CryptUpdateKeys(this._password[i]);
            }
        }

        private void CryptUpdateKeys(char c)
        {
            this._keys[0] = CryptCRC32(this._keys[0], c);
            this._keys[1] += this._keys[0] & 0xff;
            this._keys[1] = (this._keys[1] * 0x8088405) + 1;
            c = (char)(this._keys[1] >> 0x18);
            this._keys[2] = CryptCRC32(this._keys[2], c);
        }

        public override void Flush()
        {
            this._baseStream.Flush();
        }

        internal bool InitKeys()
        {
            this.CryptInitKeys();
            return this.CryptCheck();
        }

        public override int Read(byte[] buf, int offset, int count)
        {
            count = this._baseStream.Read(buf, offset, count);
            this.CryptDecodeBuffer(buf, count);
            return count;
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return this._baseStream.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            this._baseStream.SetLength(value);
        }

        public override void Write(byte[] buf, int offset, int count)
        {
            this.CryptEncodeBuffer(buf, offset, count);
            this._crcValue = this._crc.checkSum(this._crcValue, buf, offset, count);
            this._baseStream.Write(buf, offset, count);
        }

        // Properties
        public override bool CanRead
        {
            get
            {
                return this._baseStream.CanRead;
            }
        }

        public override bool CanSeek
        {
            get
            {
                return this._baseStream.CanSeek;
            }
        }

        public override bool CanWrite
        {
            get
            {
                return this._baseStream.CanWrite;
            }
        }

        public override long Length
        {
            get
            {
                return this._baseStream.Length;
            }
        }

        public override long Position
        {
            get
            {
                return this._baseStream.Position;
            }
            set
            {
                this._baseStream.Position = value;
            }
        }
    }
}
